package com.yummy.cloud.gateway.manage.service;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.cloud.gateway.event.FilterArgsEvent;
import org.springframework.cloud.gateway.event.PredicateArgsEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.support.ConfigurationService;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import com.yummy.cloud.gateway.manage.dto.RouteDefinitionQuery;
import com.yummy.cloud.gateway.route.MysqlRouteDefinitionRepository;
import com.yummy.convention.exception.AssertUtil;
import com.yummy.convention.exception.BusinessException;
import com.yummy.convention.page.Pagination;
@Service
public class RouteDefinitionService{

	@Autowired
	MysqlRouteDefinitionRepository mysqlRouteDefinitionRepository;
	@Autowired
	InfrastructureService infrastructureService;
	@Autowired
	ConfigurationService configurationService;
	
	public Pagination<RouteDefinition> getRouteDefinitions(RouteDefinitionQuery query) {
		return mysqlRouteDefinitionRepository.getCurrentRouteDefinitions(query);
	}

	public Boolean create(RouteDefinition route) {
		validateRouteDefinition(route);
		mysqlRouteDefinitionRepository.create(route);
		return true;
	}

	private void validateRouteDefinition(RouteDefinition route) {
		validatePredicates(route.getPredicates(),route.getId());
		validateFilters(route.getFilters(),route.getId());
	}

	private void validateFilters(List<FilterDefinition> filters, String routeId) {
		if(CollectionUtils.isEmpty(filters)) {
			return;
		}
		filters.stream().forEach(f -> {
			validateFilter(f,routeId);
		});
		
	}

	private void validateFilter(FilterDefinition f, String routeId) {
		Map<String,GatewayFilterFactory> filterFactoryMap = infrastructureService.getFilterFactoryMap();
		AssertUtil.isTrue(filterFactoryMap.containsKey(f.getName()),"找不到名称是 "+f.getName()+" 的过滤器");
		GatewayFilterFactory<Object> ff = filterFactoryMap.get(f.getName());
		validateConfig(ff,f,routeId);
	}

	private void validateConfig(GatewayFilterFactory<Object> ff, FilterDefinition f,String routeId) {
		NamedConfig namedConfig = toNamedConfig(ff,f);
		try {
			Object config = this.configurationService.with(ff)
				.name(namedConfig.getName())
				.properties(namedConfig.getProperties())
				.eventFunction((bound, properties) -> new FilterArgsEvent(
						// TODO: why explicit cast needed or java compile fails
						this, routeId, (Map<String, Object>) properties))
				.bind();
		}catch (BindException e) {
			e.printStackTrace();
			throw new BusinessException(e.getMessage());
		}
	}

	private NamedConfig toNamedConfig(GatewayFilterFactory ff, FilterDefinition f) {
		NamedConfig config = new NamedConfig();
		config.setName(ff.name());
		config.setProperties(f.getArgs());
		return config;
	}

	private void validatePredicates(List<PredicateDefinition> predicates, String routeId) {
		AssertUtil.notEmpty(predicates, "匹配器不能为空");
		predicates.stream().forEach(p -> {
			validatePredicate(p,routeId);
		});
		
	}

	private void validatePredicate(PredicateDefinition p, String routeId) {
		Map<String,RoutePredicateFactory> predicateFactoryMap = infrastructureService.getPredicateFactoryMap();
		AssertUtil.isTrue(predicateFactoryMap.containsKey(p.getName()),"找不到名称是 "+p.getName()+" 的匹配器");
		RoutePredicateFactory<Object> pf = predicateFactoryMap.get(p.getName());
		validateConfig(pf,p,routeId);
	}

	private void validateConfig(RoutePredicateFactory<Object> pf, PredicateDefinition p, String routeId) {
		NamedConfig namedConfig = toNamedConfig(pf,p);
		try {
			Object config = this.configurationService.with(pf)
				.name(namedConfig.getName())
				.properties(namedConfig.getProperties())
				.eventFunction((bound, properties) -> new PredicateArgsEvent(
						this, routeId, properties))
				.bind();
		}catch (BindException e) {
			e.printStackTrace();
			throw new BusinessException(e.getMessage());
		}
	}

	private NamedConfig toNamedConfig(RoutePredicateFactory pf,PredicateDefinition p) {
		NamedConfig config = new NamedConfig();
		config.setName(pf.name());
		config.setProperties(p.getArgs());
		return config;
	}

	public Boolean delete(String routeId) {
		mysqlRouteDefinitionRepository.delete(routeId);
		return true;
	}

	public static class NamedConfig{
		private String name;
		private Map<String,String> properties;
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public Map<String,String> getProperties() {
			return properties;
		}
		public void setProperties(Map<String,String> properties) {
			this.properties = properties;
		}
		
	}

	public Boolean update(RouteDefinition route) {
		validateRouteDefinition(route);
		mysqlRouteDefinitionRepository.update(route);
		return true;
	}
}
